/* ==================================================================
* Copyright (c) 2024/11/15 SCNU LiuJunJie z-ken
* All rights reserved.
* fileName:	BuildFromDFA.hpp
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the xxx Group. and
* its contributors.
* 4. Neither the name of the Group nor the names of its contributors may
* be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY Author,GROUP AND CONTRIBUTORS
* ===================================================================
* Author: LiuJunJie z-ken.
*/

#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <sstream>
#include <fstream>
#include <string.h>
#include <direct.h>
#include <io.h>
using namespace std;

#if !defined(BUILD_FROM_DFA_HPP)
#define BUILD_FROM_DFA_HPP

class BUILD_ERROR
{
public:
    BUILD_ERROR(string res)
    {
    }
};

class BuildFromDFA
{
public:
    map<int, map<string, int>> state_DFA;
    set<int> startState;                             // 变换为状态DFA后，分类为终点state和起点state
    set<int> endState;                               // 变换为状态DFA后，分类为终点state和起点state
    int begin;                                       // 初始状态
    string filePath;                                 // 输出普通的分散的代码文件的路径
    bool needPause = true;                           // 是否需要生成的exe暂停
    bool startCmd = true;                            // 是否在activeConsole时刻开启cmd
    bool onlyFunc = false;                           // 是否只保留函数
    bool writeTokenValue = true;                     // 是否需要在写入文件output的tokenType时候，把tokenvalue一起写入，即是单文件模式还是双文件编码模式
    set<string> saveTurnning = set<string>({"\\n"}); // 检测是需要保留转义字符，比如，文本的原生转义字符\n需要保留，
    string funcPrefix = "match_";                    // 函数前缀
    string baseDir = "./generate";                   // 生成文件的目标文件夹
    string jointFilePath = "./generate/joint.cpp";   // 生成的文件名称，由于是名称，所以会受到baseDir影响
    vector<string> ignoreOutputTokenType = {"_doc"}; // 需要忽略输出的正则类型
    vector<string> match_func_body;                  // 用于存放函数体的文本，

    BuildFromDFA() {};
    BuildFromDFA(map<int, map<string, int>> stateDFA, set<int> startState, set<int> endState)
    {
        // 创建文件夹
        if (_access(baseDir.c_str(), 0) == -1) // 如果不存在此文件
        {
            _mkdir(baseDir.c_str());
        }

        this->state_DFA = stateDFA;
        this->startState = startState;
        this->endState = endState;
        vector<int> temp(startState.begin(), startState.end());
        if (temp.size() == 0)
        {
            throw BUILD_ERROR("发生错误于 BuildFromDFA, 不存在起点");
        }
        begin = temp[0];
    }

    // 书写生成状态转移代码
    string generateCode(string funcName)
    {
        stringstream code;
        // 头文件
        if (onlyFunc == false)
        {
            code << "const int NEED_PAUSE=" << needPause << ";\n";
            code << "#include <iostream>" << "\n";
            code << "#include <string>" << "\n";
            code << "using namespace std;" << "\n";
        }

        // 函数定义
        code << "bool " << funcName << "(string s) \n";
        code << "{\n";
        // {
        //     set<int> ends;

        code << "\t" << "int idx = 0;" << "\n";
        code << "\t" << "int state = " << begin << ';' << "\n";
        code << "\t" << "while (idx < s.length())" << "\n";
        code << "\t" << "{" << "\n";
        code << "\t\t" << "switch (state)" << "\n";
        code << "\t\t" << "{" << "\n";
        for (auto pi : state_DFA) // 最多志愿size个状态
        {
            code << "\t\t\t" << "case\t" << pi.first << ":" << "\n"; // 第一层状态
            // 第二层switch
            code << "\t\t\t\t" << "switch (" << "s[idx]" << ")" << "\n";
            code << "\t\t\t\t" << "{" << "\n";
            for (auto pi2 : pi.second) // 最多志愿size个状态
            {
                if (pi2.second != -1)
                {
                    string pi2_first = pi2.first;
                    // 如果遇到转义字符
                    if (pi2.first[0] == '\\')
                    {
                        // 如果是转义字符开头，检测转义字符是否需要保留
                        if (saveTurnning.find(pi2.first) != saveTurnning.end()) // 不需要变化
                        {
                            pi2_first = pi2.first;
                        }
                        else // 将转义输入的符号去除，不保留转义
                        {
                            pi2_first = pi2.first.substr(1);
                        }
                    }
                    code << "\t\t\t\t\t" << "case\t" << "'" << pi2_first << "'" << ":" << "\n";
                    code << "\t\t\t\t\t\t" << "state = " << pi2.second << ";" << "\n";
                    code << "\t\t\t\t\t\t" << "break;" << "\n";
                }
            }

            code << "\t\t\t\t\t" << "default:\n";
            code << "\t\t\t\t\t\t" << "state = " << state_DFA.size() + 1 << ";\n";
            code << "\t\t\t\t\t\t" << "break;" << "\n";
            code << "\t\t\t\t" << "}" << "\n";
            code << "\t\t\t\t" << "break;" << '\n';
        }

        code << "\t\t\t" << "default:\n";
        code << "\t\t\t\t" << "state = " << state_DFA.size() + 1 << ";\n";
        code << "\t\t\t\t" << "break;" << "\n";
        code << "\t\t" << "}" << "\n";
        code << "\t\t" << "idx++" << ";" << "\n";
        code << "\t" << "}" << "\n"; // while循环结束

        code << "\t" << "if (";
        vector<int> v_temp(endState.begin(), endState.end());
        for (int i = 0; i < v_temp.size(); i++)
        {
            code << "state == " << v_temp[i];
            if (v_temp.size() - 1 != i)
            {
                code << " || "; // 或符号
            }
        }
        code << ")" << "\n";
        code << "\t" << "{" << '\n';
        code << "\t\t" << "return true;" << '\n'; // 离开
        code << "\t" << "}" << '\n';
        code << "\t" << "return false;" << "\n";
        code << "}" << "\n"; // 函数结束

        // main函数
        if (onlyFunc == false)
        {
            code << "int main()" << "\n";

            code << "{" << "\n";
            code << "\t" << "cout << \"Input a string try to match RE: \"<< endl;" << "\n";
            code << "\t" << "string input;" << "\n";
            code << "\t" << "getline(cin, input);" << "\n";
            code << "\t" << "bool res = " << funcName << "(input);" << "\n";
            code << "\t" << "if (res)" << "\n";
            code << "\t" << "{" << "\n";
            code << "\t\t" << "cout << \"success, the string was match the RE\"<< endl;" << "\n";
            code << "\t" << "}" << "\n";
            code << "\t" << "else" << "\n";
            code << "\t" << "{" << "\n";
            code << "\t\t" << "cout << \"FAIL, the string NOT match the RE\" << endl;" << "\n";
            code << "\t" << "}" << "\n";
            code << "\t" << "cout<<\"res:\"" << "<< res" << ";" << "\n";
            code << "\t" << "if(NEED_PAUSE) system(\"pause\")" << ";" << "\n";
            code << "\treturn 0" << ";" << "\n";
            code << "}" << "\n";
        }

        string res = code.str();

        return res;
    }

    void writeCode(string filePath, string tokenName)
    {
        this->filePath = filePath;
        ofstream ofs(filePath);
        ofs << generateCode(funcPrefix + tokenName);
        ofs.close();
    }

    void activeConsole(string exePath)
    {
        if (filePath.empty())
        {
            cout << "请设置文件" << endl;
        }
        string s = "g++ " + filePath + " -o " + exePath;
        system(s.c_str());

        if (startCmd)
        {
            string commandStr = string("start") + string(" ") + exePath;
            system(commandStr.c_str());
        }
    }

    class BUILD_ERR
    {
        string msg;

    public:
        BUILD_ERR(string s)
        {
            msg = s;
        }
    };

    /**
     * 工具方法
     * use_match_func_body - 是否使用存在于内部的函数body
     */
    void jointTypeFile(vector<string> cppFilePaths, vector<string> reNames, int use_match_func_body)
    {
        cout << "start to generate jointFile" << endl;
        if (reNames.size() < 1)
        {
            throw new BUILD_ERR("you need at least one re");
        }

        stringstream code;
        // 头文件
        code << "#include <iostream>" << "\n";
        code << "#include <string>" << "\n";
        code << "#include <fstream>" << "\n";
        code << "#include <unordered_map>" << "\n";
        code << "#include <unordered_set>" << "\n";
        code << "#include<vector>" << "\n";
        code << "#include<string>" << "\n";
        code << "#include<set>" << "\n";
        code << "using namespace std;" << "\n";
        // 将生成函数实体都输入进去
        if (use_match_func_body)
        {
            for (int i = 0; i < match_func_body.size(); i++)
            {
                code << "\n";
                code << match_func_body[i];
                code << "\n";
            }
        }
        else
        {
            for (int i = 0; i < cppFilePaths.size(); i++)
            {
                code << "\n";
                vector<string> readRows = UtilFunc::readFile(cppFilePaths[i]);
                for (string line : readRows)
                {
                    code << line << "\n";
                }
                code << "\n";
            }
        }

        // 进行代码模板填写，将自定义的常量都加入进去
        code << "unordered_map<string, int> tokenTypeMap;" << "\n";
        code << "vector<string> tokenTypes;" << "\n";
        code << "vector<string> tokenStrings;" << "\n";
        code << "vector<char> readChars;" << "\n";

        code << "set<char> ignoreChar = {'\\n', ' '};" << "\n";
        code << "set<string> ignoreOutputTokenType = {";
        for (int t = 0; t < ignoreOutputTokenType.size(); t++)
        {
            code << "\"" << ignoreOutputTokenType[t] << "\"";
            if (t != ignoreOutputTokenType.size() - 1)
            {
                code << ", ";
            }
        }
        code << "};" << "\n";
        code << "int idx;" << "\n";
        code << "string readFilePath = \"./joint_test.txt\";" << "\n";
        code << "string outPutPath = \"./output.txt\";" << "\n";
        code << "string outPutContentPath = \"./outputContent.txt\";" << "\n";
        code << "bool couldBeMoreLonger(string s){" << "\n";
        code << string(2, '\t') << "return ";
        for (int i = 0; i < reNames.size(); i++)
        {
            code << "\n";
            code << string(3, '\t') << funcPrefix + reNames[i] << "(s)";
            // 如果不是最后一条
            if (i == reNames.size() - 1)
            {
                code << ";\n";
            }
            else
            {
                code << "||" << "\n";
            }
        }
        code << "}" << "\n";

        code << "void readFileFull(string filePath)" << "\n";
        code << "{" << "\n";
        code << "    string data;" << "\n";
        code << "    ifstream ifs(filePath);" << "\n";
        code << "    while (std::getline(ifs, data)) // ifs，进行getLine的读取方式会省略换行符" << "\n";
        code << "    {" << "\n";
        code << "        for(int i=0;i<data.size();i++)" << "\n";
        code << "        {" << "\n";
        code << "            readChars.push_back(data[i]);" << "\n";
        code << "        }" << "\n";
        code << "         readChars.push_back('\\n');" << "\n";
        code << "    }" << "\n";
        code << "    ifs.close();" << "\n";
        code << "}" << "\n";

        // 写入文件函数
        code << string(0, '\t') << "void outPutFile()" << "\n";
        code << string(0, '\t') << "{" << "\n";
        code << string(1, '\t') << "try" << "\n";
        code << string(1, '\t') << "{" << "\n";

        code << string(2, '\t') << "ofstream ofs(outPutPath);" << "\n";
        code << string(2, '\t') << "for (int i = 0; i < tokenTypes.size(); i++)" << "\n";
        code << string(2, '\t') << "{" << "\n";
        code << string(3, '\t') << "if (ignoreOutputTokenType.find(tokenTypes[i]) == ignoreOutputTokenType.end())" << "\n";
        code << string(3, '\t') << "{" << "\n";
        code << string(4, '\t') << "ofs << tokenTypes[i] << '\\n';" << "\n";
        if (writeTokenValue == true)
        {
            code << string(4, '\t') << "ofs << tokenStrings[i] << '\\n';" << "\n";
        }
        code << string(3, '\t') << "}" << "\n";
        code << string(2, '\t') << "\n";
        code << string(2, '\t') << "}" << "\n";
        code << string(2, '\t') << "ofs.close();" << "\n";
        // 第二个文件
        if (writeTokenValue == false)
        {
            code << string(2, '\t') << "ofstream ofs2(outPutContentPath);" << "\n";
            code << string(2, '\t') << "for (int i = 0; i < tokenTypes.size(); i++)" << "\n";
            code << string(2, '\t') << "{" << "\n";
            code << string(3, '\t') << "if (ignoreOutputTokenType.find(tokenTypes[i]) == ignoreOutputTokenType.end())" << "\n";
            code << string(3, '\t') << "{" << "\n";
            code << string(4, '\t') << "ofs2 << tokenStrings[i] <<'\\n';" << "\n";
            code << string(3, '\t') << "}" << "\n";
            code << string(2, '\t') << "}" << "\n";
            code << string(2, '\t') << "ofs2.close();" << "\n";
        }

        code << string(1, '\t') << "}" << "\n"; // try的结束位置

        // 捕获异常，避免文件指针异常
        code << string(1, '\t') << "catch (const std::exception &e)" << "\n";
        code << string(1, '\t') << "{" << "\n";
        code << string(1, '\t') << "std::cerr << e.what() << endl;" << "\n";
        code << string(1, '\t') << "}" << "\n";
        code << string(0, '\t') << "}" << "\n";
        code << "\n";
        // // 读取一个字符
        code << "char getOneLetter()" << "\n";
        code << "{" << "\n";
        code << "    if (idx >= readChars.size())" << "\n";
        code << "    {" << "\n";
        code << "        return -1; // 阅读结束" << "\n";
        code << "    }" << "\n";
        code << "    char retChar = readChars[idx];" << "\n";
        code << "    idx = idx + 1;" << "\n";
        code << "    return retChar;" << "\n";
        code << "}" << "\n";
        code << "\n";
        code << "void backOneLetter()" << "\n";
        code << "{" << "\n";
        code << "    // 拒绝回退" << "\n";
        code << "    if (idx == 0)" << "\n";
        code << "    {" << "\n";
        code << "        return;" << "\n";
        code << "    }" << "\n";
        code << "    else" << "\n";
        code << "    {" << "\n";
        code << "        idx = idx - 1;" << "\n";
        code << "    }" << "\n";
        code << "}" << "\n";
        code << "\n";
        code << "int getTokenType()" << "\n";
        code << "{" << "\n";
        code << string(1, '\t') << "// 初始化" << "\n";
        code << string(1, '\t') << "readFileFull(readFilePath);" << "\n";
        code << string(1, '\t') << "char c;" << "\n";
        code << string(1, '\t') << "string s;" << "\n";
        code << string(1, '\t') << "string currTokenType = \"NONE\";" << "\n";
        code << string(1, '\t') << "while ((c = getOneLetter()) != -1)" << "\n";
        code << string(1, '\t') << "{" << "\n";
        code << string(2, '\t') << "// 不进行读入" << "\n";
        code << string(2, '\t') << "if (ignoreChar.find(c) != ignoreChar.end())" << "\n";
        code << string(2, '\t') << "{" << "\n";
        code << string(3, '\t') << "continue;" << "\n";
        code << string(2, '\t') << "}" << "\n";
        code << "\n";
        code << string(2, '\t') << "s.push_back(c);" << "\n";
        code << "\n";
        code << string(2, '\t') << "if" << "(" << funcPrefix + reNames[0] << "(s)" << ")" << "\n";
        code << string(2, '\t') << "{" << "\n";
        code << string(3, '\t') << "currTokenType = " << "\"" << reNames[0] << "\";" << "\n";
        code << string(2, '\t') << "}" << "\n";
        for (int i = 1; i < reNames.size(); i++)
        {
            code << string(2, '\t') << "else if (" << funcPrefix + reNames[i] << "(s))" << "\n";
            code << string(2, '\t') << "{" << "\n";
            code << string(3, '\t') << "currTokenType =" << "\"" << reNames[i] << "\";" << "\n";
            code << string(2, '\t') << "}" << "\n";
        }
        code << string(2, '\t') << "// 检测能否更多的字符" << "\n";
        code << string(2, '\t') << "if (currTokenType != \"NONE\")" << "\n";
        code << string(2, '\t') << "{" << "\n";
        code << string(3, '\t') << "c = getOneLetter();// 其实这里的c保留分隔符，就是完成了空格截断" << "\n";
        code << string(3, '\t') << "if (c != -1)" << " // 不为-1时刻加入，检测回退" << "\n";
        code << string(3, '\t') << "{" << "\n";
        code << string(4, '\t') << "s.push_back(c);" << "\n";
        code << string(4, '\t') << "bool couldMore = couldBeMoreLonger(s);" << "\n";
        code << string(4, '\t') << "// 不为-1时刻回退" << "\n";
        code << string(4, '\t') << "// 回退" << "\n";
        code << string(4, '\t') << "s.pop_back();" << "\n";
        code << string(4, '\t') << "backOneLetter();" << "\n";
        code << string(4, '\t') << "if(couldMore)" << "\n";
        code << string(4, '\t') << "{" << "\n";
        code << string(5, '\t') << "continue;" << "\n";
        code << string(4, '\t') << "}" << "\n";
        code << string(3, '\t') << "}" << "\n";
        code << string(3, '\t') << "tokenTypes.push_back(currTokenType);" << "\n";
        code << string(3, '\t') << "tokenStrings.push_back(s);" << "\n";
        code << string(3, '\t') << "currTokenType=" << "\"NONE\";" << "\n";
        code << string(3, '\t') << "s.clear();" << "\n";
        code << string(2, '\t') << "}" << "\n";
        code << string(1, '\t') << "}" << "\n";
        // 判断是否有未处理的

        code << string(2, '\t') << "if(!s.empty()){" << "\n";
        code << string(3, '\t') << "tokenTypes.push_back(\"NONE\");" << "\n";
        code << string(3, '\t') << "tokenStrings.push_back(\"NONE\");" << "\n";
        code << string(2, '\t') << "}" << "\n";

        // 返回
        code << string(1, '\t') << "return 0;" << "\n";
        code << string(0, '\t') << "}" << "\n";

        // 为code加入main函数

        code << "int main(int argc, char *argv[])" << "\n";
        code << string(0, '\t') << "{" << "\n";
        code << string(1, '\t') << "if (argc == 4)" << "\n";
        code << string(1, '\t') << "{" << "\n";
        code << string(2, '\t') << "readFilePath = string(argv[1]);" << "\n";
        code << string(2, '\t') << "outPutPath = string(argv[2]);" << "\n";
        code << string(2, '\t') << "outPutContentPath = string(argv[3]);" << "\n";
        code << string(1, '\t') << "}" << "\n";
        code << string(1, '\t') << "if (argc == 3)" << "\n";
        code << string(1, '\t') << "{" << "\n";
        code << string(2, '\t') << "readFilePath = string(argv[1]);" << "\n";
        code << string(2, '\t') << "outPutPath = string(argv[2]);" << "\n";
        code << string(2, '\t') << "outPutContentPath = \"\";" << "\n";
        code << string(1, '\t') << "}" << "\n";

        // 正式执行获取token
        code << string(1, '\t') << "getTokenType();" << "\n";

        code << string(1, '\t') << "outPutFile();" << "\n";
        code << string(0, '\t') << "}" << "\n";

        // 写入文件
        ofstream ofs(jointFilePath);
        ofs << code.str();
        ofs.close();
        cout << "end joint" << endl;
    }
};

#endif // BUILD_FROM_DFA_HPP
